package clock

import (
	"fmt"
	"github.com/jacktea/z-cron/cron"
	"github.com/jacktea/z-cron/rbtree"
	"log"
	"runtime/debug"
	"sync/atomic"
	"time"
)

// Job External access interface for timed tasks
type Job interface {
	C() <-chan Job     //C Get a Chan，which can get message if Job is executed
	Count() uint64     //计数器，表示已执行（或触发）的次数
	Max() uint64       //允许执行的最大次数
	Cancel()           //撤销加载的任务，不再定时执行
	isAvailable() bool //return true，if job not action or not cancel
	GetCreateTime() time.Time
	GetActionTime() time.Time
	GetNextTime() time.Time
}

type JobFunc func() bool

func emptyJobFunc() bool {
	return true
}

// jobItem implementation of  "Job" interface and "rbtree.Item" interface
type jobItem struct {
	id          uint64 //唯一键值，内部由管理器生成，以区分同一时刻的不同任务事件
	actionCount uint64 //计数器，表示已执行（或触发）的次数
	actionMax   uint64 //允许执行的最大次数
	//intervalTime time.Duration //间隔时间
	createTime time.Time //创建时间，略有误差
	lastTime   time.Time // 最近一次已经执行的时间
	actionTime time.Time //计算得出的最近一次将要执行时间点
	schedule   cron.Schedule
	fn         JobFunc  //事件函数
	delFn      JobFunc  // 删除时函数
	msgChan    chan Job //消息通道，执行时，控制器通过该通道向外部传递消息
	cancelFlag int32
	clock      *Clock
}

// Less Based rbtree ，implements Item interface for sort
func (je jobItem) Less(another rbtree.Item) bool {
	item, ok := another.(*jobItem)
	if !ok {
		return false
	}
	if !je.actionTime.Equal(item.actionTime) {
		return je.actionTime.Before(item.actionTime)
	}
	return je.id < item.id
}

func (je *jobItem) C() <-chan Job {
	return je.msgChan
}

func (je *jobItem) action(async bool) bool {
	je.actionCount++
	je.lastTime = je.actionTime
	ret := true
	if async {
		go safeCall(je.fn)
	} else {
		ret = safeCall(je.fn)
	}
	je.actionTime = je.schedule.Next(je.actionTime)
	//fmt.Println(je.id)
	//je.actionTime = je.schedule.Next(time.Now())
	if je.isAvailable() {
		select {
		case je.msgChan <- je:
		default:
			//some times,client should not receive msgChan,so must discard jobItem when blocking
		}
	}
	return ret
}

// 执行任务，返回 '是否继续'
func (je *jobItem) exec() {
	if je.schedule.DependOnCb() {
		go func() {
			if je.action(false) {
				je.onlyCancel()
			} else {
				if !je.actionTime.IsZero() {
					go je.clock.insertJob(je)
				} else {
					je.onlyCancel()
				}
			}
		}()
	} else {
		je.action(true)
		if (je.actionMax == 0 || je.actionMax > je.actionCount) && !je.actionTime.IsZero() {
			go je.clock.insertJob(je)
		} else {
			je.onlyCancel()
		}
	}
}

func (je *jobItem) GetNextTime() time.Time {
	if je.schedule == nil {
		return time.Time{}
	}
	return je.schedule.Next(je.actionTime)
}

func (je *jobItem) Cancel() {
	if atomic.CompareAndSwapInt32(&je.cancelFlag, 0, 1) {
		je.clock.rmJob(je)
		je.innerCancel()
	}
}
func (je *jobItem) isAvailable() bool {
	return je.cancelFlag == 0
}

func (je *jobItem) onlyCancel() {
	if atomic.CompareAndSwapInt32(&je.cancelFlag, 0, 1) {
		je.innerCancel()
	}
}

func (je *jobItem) innerCancel() {
	fmt.Println(je.id, "cancel...")
	je.clock = nil
	close(je.msgChan)
	go safeCall(je.delFn)
}

// Count implement for Job
func (je jobItem) Count() uint64 {
	return je.actionCount
}

// Max implement for Job
func (je jobItem) Max() uint64 {
	return je.actionMax
}

func (je jobItem) GetActionTime() time.Time {
	return je.actionTime
}

func (je jobItem) GetCreateTime() time.Time {
	return je.createTime
}

func safeCall(fn JobFunc) bool {
	defer func() {
		if err := recover(); err != nil {
			log.Printf("[clock] recovering reason is %+v. More detail:", err)
			log.Println(string(debug.Stack()))
		}
	}()
	return fn()
}
